package cn.dkdoo.utils.templateDefault.mongo;

import cn.dkdoo.utils.config.rollbackData.RollbackDetail;
import com.mongodb.*;
import org.bson.types.ObjectId;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.mongodb.core.*;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.mapreduce.GroupBy;
import org.springframework.data.mongodb.core.mapreduce.GroupByResults;
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
import org.springframework.data.mongodb.core.mapreduce.MapReduceResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.util.CloseableIterator;
import org.springframework.util.CollectionUtils;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static org.springframework.data.mongodb.core.query.Criteria.where;

/**
 * @author nicky
 * @date 2021/5/10 下午1:08
 */
public class DefaultMongoTemplate extends RollbackMongoTemplate implements MongoOperations, ApplicationContextAware {

    private RollbackMongoTemplate proxyTemplate;//代理类，主要处理事物关闭的情况

    public DefaultMongoTemplate(MongoTemplate template, String beanName) {
        super(template, beanName);
    }

    @Override
    public String getCollectionName(Class<?> aClass) {
        return super.template.getCollectionName(aClass);
    }

    @Override
    public CommandResult executeCommand(String s) {
        return super.template.executeCommand(s);
    }

    @Override
    public CommandResult executeCommand(DBObject dbObject) {
        return super.template.executeCommand(dbObject);
    }

    @Override
    public CommandResult executeCommand(DBObject dbObject, int i) {
        return super.template.executeCommand(dbObject, i);
    }

    @Override
    public CommandResult executeCommand(DBObject dbObject, ReadPreference readPreference) {
        return super.template.executeCommand(dbObject, readPreference);
    }

    @Override
    public void executeQuery(Query query, String s, DocumentCallbackHandler documentCallbackHandler) {
        super.template.executeQuery(query, s, documentCallbackHandler);
    }

    @Override
    public <T> T execute(DbCallback<T> dbCallback) {
        return super.template.execute(dbCallback);
    }

    @Override
    public <T> T execute(Class<?> aClass, CollectionCallback<T> collectionCallback) {
        return super.template.execute(aClass, collectionCallback);
    }

    @Override
    public <T> T execute(String s, CollectionCallback<T> collectionCallback) {
        return super.template.execute(s, collectionCallback);
    }

    @Override
    public <T> T executeInSession(DbCallback<T> dbCallback) {
        return super.template.executeInSession(dbCallback);
    }

    @Override
    public <T> CloseableIterator<T> stream(Query query, Class<T> aClass) {
        return super.template.stream(query, aClass);
    }

    @Override
    public <T> DBCollection createCollection(Class<T> aClass) {
        return super.template.createCollection(aClass);
    }

    @Override
    public <T> DBCollection createCollection(Class<T> aClass, CollectionOptions collectionOptions) {
        return super.template.createCollection(aClass, collectionOptions);
    }

    @Override
    public DBCollection createCollection(String s) {
        return super.template.createCollection(s);
    }

    @Override
    public DBCollection createCollection(String s, CollectionOptions collectionOptions) {
        return super.template.createCollection(s, collectionOptions);
    }

    @Override
    public Set<String> getCollectionNames() {
        return super.template.getCollectionNames();
    }

    @Override
    public DBCollection getCollection(String s) {
        return super.template.getCollection(s);
    }

    @Override
    public <T> boolean collectionExists(Class<T> aClass) {
        return super.template.collectionExists(aClass);
    }

    @Override
    public boolean collectionExists(String s) {
        return super.template.collectionExists(s);
    }

    @Override
    public <T> void dropCollection(Class<T> aClass) {
        super.template.dropCollection(aClass);
    }

    @Override
    public void dropCollection(String s) {
        super.template.dropCollection(s);
    }

    @Override
    public IndexOperations indexOps(String s) {
        return super.template.indexOps(s);
    }

    @Override
    public IndexOperations indexOps(Class<?> aClass) {
        return super.template.indexOps(aClass);
    }

    @Override
    public ScriptOperations scriptOps() {
        return super.template.scriptOps();
    }

    @Override
    public <T> List<T> findAll(Class<T> aClass) {
        return super.template.findAll(aClass);
    }

    public <T> List<T> findAll(Class<T> aClass, boolean saveTra) {
        List<T> res = super.template.findAll(aClass);
        super.addOneRollbackData(res, aClass, null, saveTra);
        return res;
    }

    @Override
    public <T> List<T> findAll(Class<T> aClass, String s) {
        return super.template.findAll(aClass, s);
    }

    public <T> List<T> findAll(Class<T> aClass, String s, boolean saveTra) {
        List<T> res = super.template.findAll(aClass, s);
        super.addOneRollbackData(res, aClass, s, saveTra);
        return res;
    }

    @Override
    public <T> GroupByResults<T> group(String s, GroupBy groupBy, Class<T> aClass) {
        return super.template.group(s, groupBy, aClass);
    }

    @Override
    public <T> GroupByResults<T> group(Criteria criteria, String s, GroupBy groupBy, Class<T> aClass) {
        return super.template.group(criteria, s, groupBy, aClass);
    }

    @Override
    public <O> AggregationResults<O> aggregate(TypedAggregation<?> typedAggregation, String s, Class<O> aClass) {
        return super.template.aggregate(typedAggregation, s, aClass);
    }

    @Override
    public <O> AggregationResults<O> aggregate(TypedAggregation<?> typedAggregation, Class<O> aClass) {
        return super.template.aggregate(typedAggregation, aClass);
    }

    @Override
    public <O> AggregationResults<O> aggregate(Aggregation aggregation, Class<?> aClass, Class<O> aClass1) {
        return super.template.aggregate(aggregation, aClass, aClass1);
    }

    @Override
    public <O> AggregationResults<O> aggregate(Aggregation aggregation, String s, Class<O> aClass) {
        return super.template.aggregate(aggregation, s, aClass);
    }

    @Override
    public <T> MapReduceResults<T> mapReduce(String s, String s1, String s2, Class<T> aClass) {
        return super.template.mapReduce(s, s1, s2, aClass);
    }

    @Override
    public <T> MapReduceResults<T> mapReduce(String s, String s1, String s2, MapReduceOptions mapReduceOptions, Class<T> aClass) {
        return super.template.mapReduce(s, s1, s2, mapReduceOptions, aClass);
    }

    @Override
    public <T> MapReduceResults<T> mapReduce(Query query, String s, String s1, String s2, Class<T> aClass) {
        return super.template.mapReduce(query, s, s1, s2, aClass);
    }

    @Override
    public <T> MapReduceResults<T> mapReduce(Query query, String s, String s1, String s2, MapReduceOptions mapReduceOptions, Class<T> aClass) {
        return super.template.mapReduce(query, s, s1, s2, mapReduceOptions, aClass);
    }

    @Override
    public <T> GeoResults<T> geoNear(NearQuery nearQuery, Class<T> aClass) {
        return super.template.geoNear(nearQuery, aClass);
    }

    @Override
    public <T> GeoResults<T> geoNear(NearQuery nearQuery, Class<T> aClass, String s) {
        return super.template.geoNear(nearQuery, aClass, s);
    }

    @Override
    public <T> T findOne(Query query, Class<T> aClass) {
        return super.template.findOne(query, aClass);
    }

    public <T> T findOneRol(Query query, Class<T> aClass) {
        return this.findOne(query, aClass, this.getCollectionName(aClass), true);
    }

    public <T> T findOne(Query query, Class<T> aClass, boolean saveTra) {
        return this.findOne(query, aClass, this.getCollectionName(aClass), saveTra);
    }

    @Override
    public <T> T findOne(Query query, Class<T> aClass, String s) {
        return super.template.findOne(query, aClass, s);
    }

    public <T> T findOneRol(Query query, Class<T> aClass, String s) {
        return this.findOne(query, aClass, s, true);
    }

    public <T> T findOne(Query query, Class<T> aClass, String s, boolean saveTra) {
        T res = super.template.findOne(query, aClass, s);
        super.addOneRollbackData(res, aClass, s, saveTra);
        return res;
    }

    @Override
    public boolean exists(Query query, String s) {
        return super.template.exists(query, s);
    }

    @Override
    public boolean exists(Query query, Class<?> aClass) {
        return super.template.exists(query, aClass);
    }

    @Override
    public boolean exists(Query query, Class<?> aClass, String s) {
        return super.template.exists(query, aClass, s);
    }

    @Override
    public <T> List<T> find(Query query, Class<T> aClass) {
        return super.template.find(query, aClass);
    }

    public <T> List<T> findRol(Query query, Class<T> aClass) {
        return this.find(query, aClass, this.getCollectionName(aClass), true);
    }

    public <T> List<T> find(Query query, Class<T> aClass, boolean saveTra) {
        return this.find(query, aClass, this.getCollectionName(aClass), saveTra);
    }

    @Override
    public <T> List<T> find(Query query, Class<T> aClass, String s) {
        return super.template.find(query, aClass, s);
    }

    public <T> List<T> findRol(Query query, Class<T> aClass, String s) {
        return this.find(query, aClass, s, true);
    }

    public <T> List<T> find(Query query, Class<T> aClass, String s, boolean saveTra) {
        List<T> res = super.template.find(query, aClass, s);
        super.addOneRollbackData(res, aClass, s, saveTra);
        return res;
    }

    @Override
    public <T> T findById(Object o, Class<T> aClass) {
        return super.template.findById(o, aClass);
    }

    public <T> T findByIdRol(Object o, Class<T> aClass) {
        return this.findById(o, aClass, this.getCollectionName(aClass), true);
    }

    public <T> T findById(Object o, Class<T> aClass, boolean saveTra) {
        return this.findById(o, aClass, this.getCollectionName(aClass), saveTra);
    }


    @Override
    public <T> T findById(Object o, Class<T> aClass, String s) {
        return super.template.findById(o, aClass, s);
    }

    public <T> T findByIdRol(Object o, Class<T> aClass, String s) {
        return this.findById(o, aClass, s, true);
    }

    public <T> T findById(Object o, Class<T> aClass, String s, boolean saveTra) {
        T res = super.template.findById(o, aClass, s);
        super.addOneRollbackData(res, aClass, s, saveTra);
        return res;
    }


    @Override
    public <T> T findAndModify(Query query, Update update, Class<T> aClass) {
        //return super.template.findAndModify(query, update, aClass);
        return this.findAndModify(query, update, new FindAndModifyOptions(), aClass, this.getCollectionName(aClass));
    }

    @Override
    public <T> T findAndModify(Query query, Update update, Class<T> aClass, String s) {
        //return super.template.findAndModify(query, update, aClass, s);
        return this.findAndModify(query, update, new FindAndModifyOptions(), aClass, s);
    }

    @Override
    public <T> T findAndModify(Query query, Update update, FindAndModifyOptions findAndModifyOptions, Class<T> aClass) {
        return this.findAndModify(query, update, findAndModifyOptions, aClass, this.getCollectionName(aClass));
    }

    @Override
    public <T> T findAndModify(Query query, Update update, FindAndModifyOptions findAndModifyOptions, Class<T> aClass, String s) {
        Map<String, RollbackDetail> rollbackDetailMap = proxyTemplate.updateBuildRolBef(query, s);
        T tRes = super.template.findAndModify(query, update, findAndModifyOptions, aClass, s);
        if (tRes != null) {
            proxyTemplate.updateBuildRolAft(tRes, rollbackDetailMap);// 可能upsert新增了一条数据
        } else {
            // findAndModifyOptions没有设置return,新增的时候，返回的是null
            BasicDBObject aft = this.template.findOne(query, BasicDBObject.class, s);
            if (aft != null && aft.containsKey("_id")) {
                proxyTemplate.updateBuildRolAft(aft.get("_id").toString(), rollbackDetailMap);
            }
        }
        super.addRollbackData(s, rollbackDetailMap);
        return tRes;
    }

    @Override
    public <T> T findAndRemove(Query query, Class<T> aClass) {
        return this.findAndRemove(query, aClass, this.getCollectionName(aClass));
    }

    @Override
    public <T> T findAndRemove(Query query, Class<T> aClass, String s) {
        Map<String, RollbackDetail> rollbackDetailMap = proxyTemplate.removeBuildRol(query, s);
        T tRes = super.template.findAndRemove(query, aClass, s);//todo 是否已经返回了旧的
        super.addRollbackData(s, rollbackDetailMap);
        return tRes;
    }

    @Override
    public long count(Query query, Class<?> aClass) {
        return super.template.count(query, aClass);
    }

    @Override
    public long count(Query query, String s) {
        return super.template.count(query, s);
    }

    @Override
    public long count(Query query, Class<?> aClass, String s) {
        return super.template.count(query, aClass, s);
    }

    @Override
    public void insert(Object o) {
        this.insert(o, this.getCollectionName(o.getClass()));
    }

    @Override
    public void insert(Object o, String s) {
        super.template.insert(o, s);
        Map<String, RollbackDetail> rollbackDetailMap = proxyTemplate.insertBuildRol(o);
        super.addRollbackData(s, rollbackDetailMap);
    }

    @Override
    public void insert(Collection<?> collection, Class<?> aClass) {
        if (CollectionUtils.isEmpty(collection)) {
            return;
        }
        super.template.insert(collection, aClass);
        Map<String, RollbackDetail> rollbackDetailMap = proxyTemplate.insertBuildRol(collection);
        super.addRollbackData(this.getCollectionName(aClass), rollbackDetailMap);
    }

    @Override
    public void insert(Collection<?> collection, String s) {
        if (CollectionUtils.isEmpty(collection)) {
            return;
        }
        super.template.insert(collection, s);
        Map<String, RollbackDetail> rollbackDetailMap = proxyTemplate.insertBuildRol(collection);
        super.addRollbackData(s, rollbackDetailMap);
    }

    @Override
    public void insertAll(Collection<?> collection) {
        if (CollectionUtils.isEmpty(collection)) {
            return;
        }
        super.template.insertAll(collection);
        proxyTemplate.insertAllBuildRol(collection);//fixme 多个表
    }

    @Override
    public void save(Object o) {
        this.save(o, this.getCollectionName(o.getClass()));
    }

    @Override
    public void save(Object o, String s) {
        String idVal = super.getIdVal(o);
        Map<String, RollbackDetail> rollbackDetailMap = null;
        if (idVal != null) {
            Query query = Query.query(where("_id").is(new ObjectId(idVal)));
            rollbackDetailMap = proxyTemplate.updateBuildRolBef(query, s);
        }

        super.template.save(o, s);

        idVal = super.getIdVal(o);
        proxyTemplate.updateBuildRolAft(idVal, rollbackDetailMap);// 可能upsert新增了一条数据
        super.addRollbackData(s, rollbackDetailMap);
    }

    @Override
    public WriteResult upsert(Query query, Update update, Class<?> aClass) {
        //return super.template.upsert(query, update, aClass);
        return this.upsert(query, update, aClass, this.getCollectionName(aClass));
    }

    @Override
    public WriteResult upsert(Query query, Update update, String s) {
//        return super.template.upsert(query, update, s);
        return this.upsert(query, update, null, s);
    }

    @Override
    public WriteResult upsert(Query query, Update update, Class<?> aClass, String s) {
        Map<String, RollbackDetail> rollbackDetailMap = proxyTemplate.updateBuildRolBef(query, s);
        WriteResult result = super.template.upsert(query, update, aClass, s);
        proxyTemplate.updateBuildRolAft(result, rollbackDetailMap);// 可能upsert新增了一条数据
        super.addRollbackData(s, rollbackDetailMap);
        return result;
    }

    @Override
    public WriteResult updateFirst(Query query, Update update, Class<?> aClass) {
        return this.updateFirst(query, update, aClass, this.getCollectionName(aClass));
    }

    @Override
    public WriteResult updateFirst(Query query, Update update, String s) {
        return this.updateFirst(query, update, null, s);
    }

    @Override
    public WriteResult updateFirst(Query query, Update update, Class<?> aClass, String s) {
        Map<String, RollbackDetail> rollbackDetailMap = proxyTemplate.updateBuildRolBef(query, s);
        WriteResult result = super.template.updateFirst(query, update, aClass, s);
        super.addRollbackData(s, rollbackDetailMap);
        return result;
    }

    @Override
    public WriteResult updateMulti(Query query, Update update, Class<?> aClass) {
        return this.updateMulti(query, update, aClass, this.getCollectionName(aClass));
    }

    @Override
    public WriteResult updateMulti(Query query, Update update, String s) {
        return this.updateMulti(query, update, null, s);
    }

    @Override
    public WriteResult updateMulti(Query query, Update update, Class<?> aClass, String s) {
        Map<String, RollbackDetail> rollbackDetailMap = proxyTemplate.updateBuildRolBef(query, s);
        WriteResult result = super.template.updateMulti(query, update, aClass, s);
        super.addRollbackData(s, rollbackDetailMap);
        return result;
    }

    @Override
    public WriteResult remove(Object o) {
        return this.remove(o, this.getCollectionName(o.getClass()));
    }

    @Override
    public WriteResult remove(Object o, String s) {
        String idVal = super.getIdVal(o);
        Map<String, RollbackDetail> rollbackDetailMap = null;
        if (idVal != null) {
            Query query = Query.query(where("_id").is(new ObjectId(idVal)));
            rollbackDetailMap = proxyTemplate.updateBuildRolBef(query, s);
        }
        WriteResult result = super.template.remove(o, s);
        super.addRollbackData(s, rollbackDetailMap);
        return result;
    }

    @Override
    public WriteResult remove(Query query, Class<?> aClass) {
        return this.remove(query, aClass, this.getCollectionName(aClass));
    }

    @Override
    public WriteResult remove(Query query, String s) {
        return this.remove(query, null, s);
    }

    @Override
    public WriteResult remove(Query query, Class<?> aClass, String s) {
        Map<String, RollbackDetail> rollbackDetailMap = proxyTemplate.removeBuildRol(query, s);
        WriteResult writeResult = super.template.remove(query, aClass, s);
        super.addRollbackData(s, rollbackDetailMap);
        return writeResult;
    }

    @Override
    public <T> List<T> findAllAndRemove(Query query, String s) {
        return this.findAllAndRemove(query, null, s);
    }

    @Override
    public <T> List<T> findAllAndRemove(Query query, Class<T> aClass) {
        return this.findAllAndRemove(query, aClass, this.getCollectionName(aClass));
    }

    @Override
    public <T> List<T> findAllAndRemove(Query query, Class<T> aClass, String s) {
        Map<String, RollbackDetail> rollbackDetailMap = proxyTemplate.removeBuildRol(query, s);
        List<T> res = super.template.findAllAndRemove(query, aClass, s);
        super.addRollbackData(s, rollbackDetailMap);
        return res;
    }

    @Override
    public MongoConverter getConverter() {
        return super.template.getConverter();
    }

//   // 批量修改，因为WriteRequest的修饰符为default,只能通过java反射去调用方法，判断类型
//   // 反射频率太高， 所以还是自己查询旧数据，手动加入回滚列表
//    public BulkWriteOperation findBulkWriteOperation(Class<?> aClass) {
//        return template.getCollection(this.getCollectionName(aClass)).initializeOrderedBulkOperation();
//    }
//
//    public BulkWriteResult bulk(BulkWriteFunction bulkWriteFunction, Class<?> aClass) throws Exception {
//        BulkWriteOperation bulkWriteOperation = findBulkWriteOperation(aClass);
//        bulkWriteFunction.buildBulk(bulkWriteOperation);
//
//        Field field = BulkWriteOperation.class.getDeclaredField("requests");
//        field.setAccessible(true);
//        List<Object> requests = (List<Object>) field.get(bulkWriteOperation);
//        for (Object request : requests) {
//          Class classU = Class.forName("com.mongodb.UpdateRequest");
//        }
//
////        bulkWriteOperation.find().update();
//        BulkWriteResult writeResult = bulkWriteOperation.execute();
//
//        return writeResult;
//    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.proxyTemplate = (RollbackMongoTemplate) applicationContext.getBean(this.beanName);
    }
}
